package com.guaoran.plugins;

import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author : guaoran
 * @Description 处理mybatis 中 表中字段存在xx.xxx 的命名，mybatis resultSetHandle 处理时当做对象.属性处理
 * @date :2019/6/2 22:17
 */
@Intercepts({
        @Signature(
                // 只能是: StatementHandler | ParameterHandler | ResultSetHandler | Executor 类或者子类
                type = Executor.class,
                //表示：拦截Executor的query方法
                method = "query",
                //args参数描述：query 有很多的重载方法，需要通过方法签名来指定具体拦截的是那个方法
                //指该query方法，有几个参数，这几个参数都是什么类型， 必须保持一致。
                // org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(java.lang.String, java.lang.Object, org.apache.ibatis.session.RowBounds).numberLine:148
                args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
        )
})
public class MybatisResultSetHandlePlugin implements Interceptor {
    final String replace = "####$$$$####";
    ThreadLocal<Boolean> threadLocal = new ThreadLocal<>();

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();
        MappedStatement ms = (MappedStatement) args[0];
        Object parameter = args[1];
        RowBounds rowBounds = (RowBounds) args[2];
        ResultHandler resultHandler = (ResultHandler) args[3];

        Executor executor = (Executor) invocation.getTarget();
        BoundSql boundSql = ms.getBoundSql(parameter);
        CacheKey cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);

        Configuration configuration = ms.getConfiguration();
        // 准备sql
        String pageSql = prepareSql(boundSql.getSql());

        BoundSql pageBoundSql = new BoundSql(configuration, pageSql, boundSql.getParameterMappings(), parameter);
        List resultList = executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, pageBoundSql);
        // 转换结果
        return conversion46Result(resultList);
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }
    @Override
    public void setProperties(Properties properties) {
    }

    /**
     * 准备sql
     * @param sql
     * @return
     */
    private String prepareSql(String sql){
        StringBuffer pageSql = new StringBuffer("SELECT ");
        String suffix = sql.substring(sql.indexOf("FROM"));
        String prefix = sql.substring(0,sql.indexOf("FROM")).replaceAll("SELECT","");
        if(prefix.indexOf(".")>0){
            prefix = prefix.replaceAll("`","");
            threadLocal.set(true);
        }
        String[] columns = prefix.split(",");
        if(threadLocal.get() != null && threadLocal.get()) {
            for (int i = 0; i < columns.length; i++) {
                String column = columns[i].trim();
                columns[i] = ("`" + column + "` as '" + column.replace(".",replace) + "'"  );
            }
        }
        for (int i = 0; i < columns.length;i++){
            pageSql.append(columns[i]);
            if (i < columns.length - 1) {
                pageSql.append(",");
            }
        }
        pageSql.append(" ").append(suffix);
        return pageSql.toString();
    }

    /**
     * 转换
     * 由于mybatis是按列对应数据， 且对列中存在对 {对象.属性} 的解析， 而我们表中的列存在{xx.xx} 中的格式，
     * 所以，在上面转换成一个特殊字符，并在此重新替换回来
     */
    private List conversion46Result(List list){
        if(threadLocal.get() != null && threadLocal.get()) {
            List<Map> resultList = Collections.synchronizedList(new ArrayList<Map>(list.size()));
            if (list != null && list.size() > 0) {
                for (int i = 0; i < list.size(); i++) {
                    Map m = (Map) list.get(i);
                    Map ma = new ConcurrentHashMap();
                    for (Object key : m.keySet()) {
                        if (String.valueOf(key).indexOf(replace) > -1) {
                            ma.put(String.valueOf(key).replace(replace, "."), m.get(key));
                        } else {
                            ma.put(key, m.get(key));
                        }
                    }
                    resultList.add(ma);
                }
            }
            return resultList;
        }
        return list;
    }
}
